package com.powernode.service.impl;

import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.powernode.constant.RabbitMQConstant;
import com.powernode.constant.RedisConstant;
import com.powernode.dto.PageResult;
import com.powernode.param.ProductQueryParam;
import com.powernode.entity.Category;
import com.powernode.entity.Product;
import com.powernode.enums.CategoryLevel;
import com.powernode.enums.ProductStatus;
import com.powernode.exception.BizException;
import com.powernode.mapper.ProductMapper;
import com.powernode.service.CategoryService;
import com.powernode.service.ProductService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.powernode.util.MinioUtil;
import org.redisson.api.RLock;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * 商品 服务实现类
 * </p>
 *
 * @author Juice
 * @since 2023-01-11
 */
@Service
@CacheConfig(cacheNames = RedisConstant.KEY_PRODUCT)
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {

    @Autowired
    private CategoryService categoryService;
    @Autowired
    private MinioUtil minioUtil;
    @Autowired
    private RabbitTemplate template;
//    @Autowired
//    private RedissonClient redissonClient;

    /**
    * 商品查询分页
    */
    @Override
    public PageResult<Product> pageQuery(ProductQueryParam param) {
        //LambdaQueryWrapper的条件构造器，字段名是动态获取的，可以使用lambda表达式
        LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(StrUtil.isNotBlank(param.getTitle()),Product::getTitle,param.getTitle());
        wrapper.eq(ObjectUtil.isNotEmpty(param.getCategoryId()),Product::getCategoryId,param.getCategoryId());
        wrapper.eq(ObjectUtil.isNotEmpty(param.getStatus()),Product::getStatus,param.getStatus());
        Page<Product> page = baseMapper.selectPage(new Page<>(param.getPageNumber(), param.getPageSize()), wrapper);

        return new PageResult<>(page.getTotal(),page.getRecords());
    }

    @Override
    /**
     * sync=true开启同步锁，在大量请求访问同一个缓存中不存在的数据时，能够保证只有第一个请求执行该注解下的方法，访问数据库
     * 其他请求都会等待缓存构建后从缓存中获取数据，这是单机版解决缓存击穿的方法
     * 如果是在分布式环境，开启了多个微服务实例，此时同步锁只能锁住当前服务实例中的请求，因为锁是基于服务实例的锁，
     * 不同服务拥有不同的锁
     * 对于分布式环境，解决缓存击穿的方式是采用分布式锁 Redisson
     * 对于getProductById这个只查询数据库的方法，配置了Redisson的sync=true默认支持分布式锁，也可以使用RLock对象手动加锁
     * 如果业务复杂，有数据查询和更改，只需锁住方法的部分代码块，可以使用RLock对象--分布式锁，
     */
    @Cacheable(sync = true)
    public Product getProductById(Long productId) {
        return this.getById(productId);
    }
    /*public Product getProductById(Long id) {

        RMap<Long, Product> rMap = redissonClient.getMap(RedisConstant.KEY_PRODUCT);
        //1.从缓存查询数据，
        Product product = rMap.get(id);

        //2.如果缓存中不存在
        if (product == null) {
            // 获取锁对象
            RLock lock = redissonClient.getLock(RedisConstant.KEY_INDEX_CATEGORY + "-" + id);
            // 尝试去获取锁
            if (lock.tryLock()) {
                try {
                    // 加锁
                    lock.lock();
                    //3.数据在缓存中不存在，访问数据库,对访问数据库的代码加锁
                    product = super.getById(id);
                    System.out.println("访问数据库....-----");
                    //4.将查询结果放入缓存
                    rMap.put(product.getId(), product);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    // 解锁
                    lock.unlock();
                }
            }
            // 获取不到锁
            else {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                getProductById(id);
            }
        }
        // 返回从缓存或者数据库查询到的数据
        return product;
    }*/

    @Override
    @CachePut(key = "#product.id")
    public Product updateProduct(Product product) {
        boolean result = this.updateById(product);
        if (!result){
            throw new BizException(HttpStatus.BAD_REQUEST.value(), "商品id不存在");
        }

        //如果商品是上架状态，发送消息到消息队列中,es库中更新文档数据
        if (product.getStatus().equals(ProductStatus.ON.getCode())){
            template.convertAndSend(RabbitMQConstant.EXCHANGE_PRODUCT,RabbitMQConstant.ROUTING_KEY_PRODUCT_SAVE,product);
        }
        return this.getById(product.getId());

    }

    @Override
    @CachePut(key = "#p0")
    public Product updateProductStatus(Long productId, Integer status) {
        Product product = this.getById(productId);
        if (product == null) {
            throw new BizException(HttpStatus.BAD_REQUEST.value(), "商品id不存在");
        }
        if (ProductStatus.ON.getCode() != status && ProductStatus.OFF.getCode() != status){
            throw new BizException(HttpStatus.BAD_REQUEST.value(), "商品状态错误: 0-未上架,1-上架");
        }
        if (ProductStatus.ON.getCode() == status) {
            if (StrUtil.isBlank(product.getImg())){
                throw  new BizException(HttpStatus.BAD_REQUEST.value(), "请先上传商品主图再上架");
            }
            if (ObjectUtil.isEmpty(product.getImgList())){
                throw new BizException(HttpStatus.BAD_REQUEST.value(), "请先完善商品详情图片再上架");
            }
            if (ObjectUtil.isEmpty(product.getDetail())){
                throw new BizException(HttpStatus.BAD_REQUEST.value(), "请完善商品详情再上架");
            }
            //如果是上架状态，发送消息到消息队列,es库新增一条商品文档
            template.convertAndSend(RabbitMQConstant.EXCHANGE_PRODUCT,RabbitMQConstant.ROUTING_KEY_PRODUCT_SAVE,product);
        }else{
            //如果是下架状态，发送消息到队列，从es库中删除一条文档
            template.convertAndSend(RabbitMQConstant.EXCHANGE_PRODUCT,RabbitMQConstant.ROUTING_KEY_PRODUCT_DEL,productId);
        }

        product.setStatus(status);
        return this.updateById(product)? this.getById(productId) : null;
    }

    @Override
    @CachePut(key = "#product.id")
    public Product saveProduct(Product product) {
        Category category = categoryService.getById(product.getCategoryId());
        if (ObjUtil.isNull(category)) {
            throw new BizException(HttpStatus.BAD_REQUEST.value(), "该商品的商品分类不存在或已删除");
        }else {
            if (CategoryLevel.LEVEL_THREE.getCode() != category.getLevel()){
                throw new BizException(HttpStatus.BAD_REQUEST.value(), "该商品不属于三级分类，不能新增");
            }
        }
        return this.save(product)? product : null;
    }

    @Override
    @CacheEvict
    public boolean removeProductById(Long productId) {
        Product product = this.getById(productId);
        if (product == null) {
            throw new BizException(HttpStatus.BAD_REQUEST.value(), "商品不存在");
        }
        if (ProductStatus.ON.getCode() == product.getStatus()){
            throw new BizException(HttpStatus.BAD_REQUEST.value(), "上架的商品不允许删除");
        }
        //删除商品信息
        boolean result = this.removeById(productId);
        //删除minio中存储的图片
        List<String> deleteImgs = new ArrayList();
        List<String> imgList = product.getImgList();
        imgList.add(product.getImg());
        imgList.stream()
                .filter(img -> img.contains("mall.imcode.top"))
                .map(img -> deleteImgs.add(img.substring(img.indexOf("top")+4))).collect(Collectors.toList());
        imgList.stream()
                .filter(img -> img.contains("192.168.100.41:9000/mall"))
                .map(img -> deleteImgs.add(img.substring(img.indexOf("mall")+5))).collect(Collectors.toList());
        minioUtil.removeBatch(deleteImgs);

        return result;

    }

    @Override
    public List<Product> listProductsByIds(List<Long> productIds) {
        return baseMapper.getProductsByIds(productIds);
    }

    /**
     * 增加库存
     */
    @Override
    public boolean incrStock(Long productId, Integer num) {

         if(baseMapper.incrStock(productId, num) == 0){
             throw new BizException(HttpStatus.FORBIDDEN.value(), "增加库存失败");
         }
         return true;
    }

    /**
     * 减少库存，会出现多线程商品超卖的情况，解决方式
     * 一：使用synchronized,lock等悲观锁对方法进行加锁，缺点是造成系统性能降低，认为所有线程安全问题一定会发送
     * 二：使用乐观锁--乐观锁关键是判断之前查询的数据是否有更新
     *      2.1 在update语句中where 判断加上版本号
     *          update pms_product set stock = stock -#{num}, version = version+1 where id = #{productId} and version = 1
     *          缺点是当多个用户都访问了同一台服务器时，版本号一致，还是会造成只有第一个用户才能执行update语句
     *      2.2 在update语句中where 判断加上stock - #{num} >=0
     *          update pms_product set stock = stock -#{num}, version = version+1 where id = #{productId} and stock - #{num} >=0
     *  下面方法中1.查询数据库2.更新数据库两者之间是两步操作，非原子操作，在并发时会出现超卖
     */
    @Override
    public boolean decrStock(Long productId, Integer num) {
        // 1.查询数据库
        Product product = baseMapper.selectById(productId);
        if (product == null) {
            throw new BizException(HttpStatus.NOT_FOUND.value(), "商品信息不存在");
        }
        // 判断还有库存,这里的判断getStock() >=0是因为sql语句update已经判断stock-num >=0,所以这里避免重复判断
        if (product.getStock() >=0){
            // 2.减少库存，数据库减少stock
            int result = baseMapper.decrStock(productId, num);
            if (result == 0) {
                throw new BizException(HttpStatus.FORBIDDEN.value(), "减少库存失败");
            }
            return true;
        }else {
            throw new BizException(HttpStatus.FORBIDDEN.value(),"库存不足");
        }

    }

}
